#第六章 使用一等函数实现设计模式

#策略模式类实现

from abc import ABC,abstractmethod
from collections import namedtuple

Customer = namedtuple("Customer",'name fidelity')

class LineItem:
    def __init__(self,product,quantity,price):
        self.product = product
        self.quantity = quantity
        self.price = price
    def total(self):
        return self.price*self.quantity

class Order: #上下文
    def __init__(self,customer,cart,promotion=None):
        self.customer = customer
        self.cart = cart
        self.promotion = promotion
    def total(self): #计算需要付款的总数（无折扣的）
        if not hasattr(self,'__tatal'):
            self.__total = sum([item.total() for item in self.cart])
        return self.__total 

    def due(self): #计算需要付款的总数（有折扣的）
        if self.promotion is None:
            discount = 0
        else:
            discount = self.promotion.discount(self)
        return self.total() - discount
    def __repr__(self):
        fmt = '<Order total: {:.2f} due: {:.2f}>'
        return fmt.format(self.total(),self.due())

'''
在Python 3.4 中，声明抽象基类最简单的方式是子类化abc.ABC。
从Python 3.0 到Python 3.3，必须在class 语句中使用
metaclass= 关键字（例如，class Promotion(metaclass=ABCMeta):）。

'''

class Promotion(ABC): #策略 抽象基类
    @abstractmethod
    def discount(self,order):
        ...


class FidelityPromo(Promotion):
    """为积分为1000或以上的顾客提供5%折扣"""
    def discount(self,order):
        return order.total()*0.05 if order.customer.fidelity>=1000 else 0

class BulkItemPromo(Promotion):
    """单个商品为20个或以上时提供10%折扣"""
    def discount(self,order):
        discount = 0
        for item in order.cart:
            if item.quantity >= 20:
                discount += item.total() * 0.1
        return discount

class LargeOrderPromo(Promotion):
    """订单中的不同商品达到10个或以上时提供7%折扣"""
    def discount(self,order):
        distinct_items = {item.product for item in order.cart }
        if len(distinct_items) >= 10:
            return order.total() * 0.7
        return 0 
        
joe = Customer('John Doe', 0)
ann = Customer('Ann Smith', 1100)
cart = [LineItem('banana', 4, .5), 
LineItem('apple', 10, 1.5),
LineItem('watermellon', 5, 5.0)]

orderJoe = Order(joe,cart,FidelityPromo())
print(orderJoe,orderJoe.total())

orderAnn = Order(ann,cart,FidelityPromo())
print(orderAnn) #同样的购物车 积分达到1000的有折扣

# 上面的策略模块 通过函数来实现
'''
只需要去掉Promotion类和其子类 然后定义各个折扣的方法
在创建Order实例的时候传入相应的方法

'''